package com.pai4j.search.service.es;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.domain.vo.dto.ChatMessageSearchResultDTO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.domain.vo.response.subjectaccount.ArticleBaseResponseVO;
import com.pai4j.search.service.AccountService;
import com.pai4j.search.service.ArticleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.pai4j.common.enums.MessageTypeEnum;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 业务数据数据同步ES
 * @author pai
 *
 * 一对一聊天消息、群聊消息、机器人对话消息、公众号文章推文消息、群组、人、公众号
 */
@Slf4j
@Service
public class Pai4jElasticSearchService extends AbstractPai4jElasticSearchClient<ChatMessageSearchResultDTO> {

    @Value("${spring.elasticsearch.message-index}")
    private String chatMessageIndex;

    @Autowired
    private AccountService accountService;
    @Autowired
    private ArticleService articleService;

    @Override
    protected String getIndexName() {
        // 聊天消息索引
        return chatMessageIndex;
    }

    /**
     * 消息搜索索引结构设计
     *
     * 该方法用于定义消息搜索的索引结构，通过创建一个描述索引字段和属性的JSON对象
     * 主要用于Elasticsearch等搜索引擎中，以确定如何存储和索引消息数据
     * XContentBuilder 是 Elasticsearch 提供的一个类，用于构建 JSON 格式的对象。
     * IOException 表示在构建 JSON 对象时可能会发生输入/输出异常（例如文件读写错误）
     * JsonXContent.contentBuilder()：创建一个 JSON 构建器。
     * .startObject()：开始定义一个 JSON 对象。
     * "properties" 是 JSON 对象中的一个键，表示索引中所有字段的定义。
     * .startObject("properties")：开始定义 "properties" 键下的内容。
     * "type" 是索引中的一个字段。
     * .field("type", "integer")：定义 "type" 字段的类型为 integer（整数）。
     * .endObject()：结束对 "type" 字段的定义。
     * "biz_id" 是另一个字段。
     * .field("type", "keyword")：定义 "biz_id" 字段的类型为 keyword（关键字）。keyword 类型通常用于精确匹配的字符串。
     * "content" 字段表示消息的内容。
     * .field("type", "text")：定义 "content" 字段的类型为 text（文本）。text 类型适用于全文搜索。
     * "create_date" 和 "update_date" 是两个日期类型的字段。
     * .field("type", "date")：定义字段类型为 date（日期）。
     * .field("format", "yyyy-MM-dd")：指定日期格式为 yyyy-MM-dd（例如：2023-10-01）。
     *
     * @return 返回描述消息搜索索引结构的JSON对象
     */
    @Override
    protected XContentBuilder getXContentBuilder() throws IOException {
        return JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")
                .startObject("type")
                .field("type", "integer")
                .endObject()
                .startObject("biz_id")
                .field("type", "keyword")
                .endObject()
                .startObject("sender_id")
                .field("type", "keyword")
                .endObject()
                .startObject("proxy_sender_id")
                .field("type", "keyword")
                .endObject()
                .startObject("receiver_id")
                .field("type", "keyword")
                .endObject()
                .startObject("content")
                .field("type", "text")
                .field("analyzer", "ik_smart")
                .field("search_analyzer", "ik_smart")
                .startObject("fields")
                    .startObject("raw")
                        .field("type", "keyword")
                        .field("ignore_above", 256)
                    .endObject()
                .endObject()
                .endObject()
                .startObject("create_date")
                .field("type", "date")
                .field("format", "yyyy-MM-dd")
                .endObject()
                .startObject("update_date")
                .field("type", "date")
                .field("format", "yyyy-MM-dd")
                .endObject()
                .endObject()
                .endObject();
    }
    
    /**
     * 将JSON字符串转换为聊天消息搜索结果DTO对象
     *
     * 该方法主要用于解析从搜索服务返回的JSON数据，并将其转换为聊天消息搜索结果DTO对象
     * 以便在应用程序中进一步处理和使用搜索结果
     *
     * @param dataJson 包含搜索结果的JSON字符串
     * @return 转换后的ChatMessageSearchResultDTO对象，如果输入为空或无效，则返回null
     */
    @Override
    protected ChatMessageSearchResultDTO convert(String dataJson) {
        if (StringUtils.isBlank(dataJson)) {
            return null;
        }
        return JsonUtil.fromJson(dataJson, ChatMessageSearchResultDTO.class);
    }
    
    /**
     * 设置搜索结果的扩展信息
     *
     * 该方法主要用于丰富搜索结果数据，通过查询相关用户和文章信息，并将其附加到搜索结果DTO中
     * 这样做可以提高数据的完整性和可读性，便于后续处理和展示搜索结果
     *
     * @param dataList 包含搜索结果DTO的列表
     */
    @Override
    protected void setSearchResultExtInfo(List<ChatMessageSearchResultDTO> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        // 获取所有用户id放到set集合中
        Set<String> userIds = new HashSet<>();
        for (ChatMessageSearchResultDTO dto : dataList) {
            userIds.add(dto.getSender());
            userIds.add(dto.getReceiver());
            if (StringUtils.isNotBlank(dto.getProxySender())) {
                userIds.add(dto.getProxySender());
            }
        }
        // 走redis批量查询用户信息
        Map<String, UserBaseResponseInfoVO> users = accountService.batchGet(userIds);
        for (ChatMessageSearchResultDTO dto : dataList) {
            dto.setSenderUser(users.get(dto.getSender()));
            dto.setReceiverUser(users.get(dto.getReceiver()));
            if (StringUtils.isNotBlank(dto.getProxySender())) {
                dto.setProxySenderUser(users.get(dto.getProxySender()));
            }
        }
        // 设置文章信息
        List<ChatMessageSearchResultDTO> articleMessages = dataList.stream().filter(dto ->
                        MessageTypeEnum.SUBJECT_ACCOUNT_ARTICLE.getType().equals(dto.getType()))
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(articleMessages)) {
            // 获取所有文章id放到set集合中:对于 SUBJECT_ACCOUNT_ARTICLE 类型的消息，message 字段存储的是文章 ID（字符串形式）
            Set<Long> articleIdSet = articleMessages.stream().map(dto -> Long.valueOf(dto.getMessage())).collect(Collectors.toSet());
            Map<Long, ArticleBaseResponseVO> articleBaseResponseVOMap = articleService.batchGet(new ArrayList<>(articleIdSet));
            for (ChatMessageSearchResultDTO dto : dataList) {
                if (MessageTypeEnum.SUBJECT_ACCOUNT_ARTICLE.getType().equals(dto.getType())) {
                    dto.setArticle(articleBaseResponseVOMap.get(Long.valueOf(dto.getMessage())));
                }
            }
        }
    }
}
